package com.sinszm.web.support;

import com.sinszm.common.Response;
import com.sinszm.common.exception.ApiException;
import com.sinszm.common.exception.SystemApiError;
import com.sinszm.common.util.CommonUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * 请求结果处理工具
 *
 * @author chenjianbo
 */
public final class RequestUtils {

    private static final Logger log = CommonUtils.logger(RequestUtils.class.getName());

    private static final List<String> MEDIA_TYPE = Arrays.asList(
            "audio/mp3",
            "video/mp4",
            "application/ogg",
            "audio/vnd.rn-realaudio",
            "application/vnd.rn-realmedia-vbr",
            "application/vnd.rn-realmedia",
            "application/vnd.rn-realplayer",
            "audio/x-pn-realaudio-plugin",
            "video/vnd.rn-realvideo",
            "audio/x-mei-aac",
            "audio/aiff",
            "audio/basic",
            "audio/x-liquid-file",
            "audio/mpegurl",
            "audio/mid",
            "audio/mp2",
            "audio/mp4",
            "audio/x-musicnet-download",
            "audio/rn-mpeg",
            "audio/x-pn-realaudio",
            "audio/wav",
            "audio/x-ms-wax",
            "audio/x-ms-wma",
            "video/x-ms-asf",
            "video/x-ivf",
            "video/x-mpeg",
            "video/x-sgi-movie",
            "video/x-mpg",
            "video/x-mpeg",
            "video/mpg",
            "video/avi",
            "video/mpeg4",
            "video/x-ms-wm",
            "video/x-ms-wmv",
            "video/x-ms-wmx",
            "video/x-ms-wvx"
    );

    /**
     * 获取流中的参数
     *
     * @param request 请求
     * @return  流中的参数
     */
    public synchronized static String requestBody(HttpServletRequest request) {
        try (
                ByteArrayOutputStream bs = new ByteArrayOutputStream();
                InputStream is = request.getInputStream();
        ) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                bs.write(buffer, 0, len);
            }
            String result = new String(bs.toByteArray(), StandardCharsets.UTF_8);
            return StringUtils.isEmpty(result) ? "" : result;
        } catch (IOException ignored) { }
        return "";
    }

    /**
     * 获取常规参数
     *
     * @param request 请求
     * @return  请求对象的参数
     */
    public synchronized static Map<String, String[]> requestParams(HttpServletRequest request) {
        return request.getParameterMap();
    }

    /**
     * 支持分片的文件下载
     * 分片播放暂仅支持MP3、MP4
     *
     * @param supplier      文件流获取业务
     * @param fileName      文件名称
     * @param contentType   文件类型
     * @param request       请求上下文
     * @return 结果信息
     */
    public synchronized static ResponseEntity<Object> responseFile(
        Supplier<InputStream> supplier,
        String fileName,
        String contentType,
        HttpServletRequest request
    ) {
        try {
            //利用函数方法取得文件流
            InputStream inputStream = Optional.ofNullable(
                    supplier.get()
            ).orElseThrow(() -> new ApiException("FILE-STREAM-NULL", "文件流不能为空"));
            //媒体类型过滤
            if (!MEDIA_TYPE.contains(contentType)) {
                throw new ApiException("MEDIA-TYPE-ERROR", "使用分片播放暂仅支持MP3、MP4");
            }
            //将文件流转换为字节数组
            byte[] iBytes = IOUtils.toByteArray(inputStream);
            //获取请求头中的分片信息
            String rangeInfo = request.getHeader("range") == null ? request.getHeader("Range") : request.getHeader("range");
            //设置默认的返回状态
            int status = StringUtils.hasText(rangeInfo) ? HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK;
            //处理分片信息
            String range = StringUtils.isEmpty(rangeInfo) || "".equals(rangeInfo.trim()) ? "bytes=0-" : rangeInfo;
            String[] nums = range.split("bytes=|-");
            //处理分片起始、结束和字节长度
            int begin = nums.length >= 2 ? Integer.parseInt(StringUtils.isEmpty(nums[1]) ? "0" : nums[1]) : 0;
            int end = nums.length >= 3 ? Integer.parseInt(StringUtils.isEmpty(nums[2]) ? (iBytes.length - 1) + "" :
                    nums[2]) : iBytes.length - 1;
            if (end > iBytes.length) {
                throw new ApiException("FILE-STREAM-ERROR", "文件分片流异常");
            }
            int len = (end - begin) + 1;
            //输出字节流处理
            byte[] nBytes = new byte[len];
            System.arraycopy(iBytes, begin, nBytes, 0, len);
            //请求的range信息打印
            log.info("range = {}, begin = {}, end = {}, length = {}, status = {}, iByteLength = {}", rangeInfo, begin, end, len, status, iBytes.length);
            //输出用的流
            InputStream nis = new ByteArrayInputStream(nBytes);
            //返回信息组装
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("Cache-Control", "no-cache, no-store, must-revalidate");
            map.add("Pragma", "no-cache");
            map.add("Expires", "0L");
            map.add("Content-Disposition", "attachment; filename=" + fileName);
            map.add("Content-Type", contentType);
            map.add("Access-Control-Allow-Origin", "*");
            if (status == HttpServletResponse.SC_PARTIAL_CONTENT) {
                map.add("Content-Range", "bytes " + begin + "-" + end + "/" + iBytes.length);
            }
            HttpHeaders headers = new HttpHeaders(map);
            ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(status);
            responseEntity.headers(headers);
            responseEntity.contentLength(nBytes.length);
            return responseEntity.body(new InputStreamResource(nis));
        } catch (Exception e) {
            ApiException apiException = new ApiException(SystemApiError.SYSTEM_ERROR_01);
            if (e instanceof ApiException) {
                apiException = (ApiException) e;
            }
            ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(500);
            responseEntity.contentType(MediaType.APPLICATION_JSON);
            return responseEntity.body(Response.fail(apiException.apiError(), e));
        }
    }

}
